﻿#include "KhaosPreHeaders.h"
#include <tchar.h>

// windows
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <timeapi.h>

#pragma comment(lib, "Winmm.lib")

HINSTANCE   g_hInst = 0;
HWND        g_hWnd = 0;
TCHAR       g_szTitle[256] = _T("VulkanTest");
TCHAR       g_szWindowClass[256] = _T("VulkanTest");

bool        g_runApp = true;

ATOM             myRegisterClass(HINSTANCE hInstance);
BOOL             initInstance(HINSTANCE, int);
void             centerWindow();
LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM);

void engineInit();
bool engineRun();
void engineShutdown();
void onWindowSizeChanged();

struct DebugConsole
{
    FILE* m_outfile;

    DebugConsole()
    {
        AllocConsole();
        m_outfile = freopen("CONOUT$", "wt", stdout);
        printf("======= Vulkan RHI Demo =======\n");
    }

    ~DebugConsole()
    {
        fclose(m_outfile);
        FreeConsole();
    }
};

class TimerPeriodFix
{
public:
    TimerPeriodFix()
    {
        timeBeginPeriod(1);
    }

    ~TimerPeriodFix()
    {
        timeEndPeriod(1);
    }
};

void _debugPrintf(const char* fmt, ...)
{
    char buff[4096] = {};
    va_list args;
    va_start(args, fmt);
    int write_n_ = vsnprintf(buff, sizeof(buff) - 1, fmt, args);
    va_end(args);
    if (0 <= write_n_ && write_n_ < sizeof(buff) - 1)
        buff[write_n_] = '\n';

    OutputDebugStringA(buff);
    printf("%s", buff);
}

std::string _getExePath()
{
    char exeName[MAX_PATH];
    GetModuleFileNameA(0, exeName, sizeof(exeName));

    char exePath[MAX_PATH];
    char* filePart;
    GetFullPathNameA(exeName, MAX_PATH, exePath, &filePart);
    *filePart = 0;

    return exePath;
}

class SimpleLog
{
public:
    SimpleLog()
    {
        std::string file = _getExePath() + "log.txt";
        m_fp = fopen(file.c_str(), "wb");
    }

    ~SimpleLog()
    {
        fclose(m_fp);
    }

    void logMsg(const char* msg)
    {
        std::string tt = msg;
        tt.push_back('\n');
        msg = tt.c_str();

        OutputDebugStringA(msg);
        printf("%s", msg);
        fputs(msg, m_fp);
        fflush(m_fp);
    }

private:
    FILE* m_fp = nullptr;
} g_log;


void _logMsg(int /*level*/, const char* fmt)
{
    g_log.logMsg(fmt);
}

void _logMsgFmt(int /*level*/, const char* fmt, ...)
{
    char buff[4096] = {};
    va_list args;
    va_start(args, fmt);
    /*int write_n_ =*/ vsnprintf(buff, sizeof(buff) - 1, fmt, args);
    va_end(args);
    g_log.logMsg(buff);
}

int APIENTRY _tWinMain(HINSTANCE hInstance,
    HINSTANCE /*hPrevInstance*/,
    LPTSTR    /*lpCmdLine*/,
    int       nCmdShow)
{
    DebugConsole dc_;
    TimerPeriodFix tpf_;

    // create window
    myRegisterClass(hInstance);

    if (!initInstance(hInstance, nCmdShow))
        return FALSE;

    centerWindow();
    engineInit();

    // run msg idle
    MSG msg = { 0 };

    while (msg.message != WM_QUIT)
    {
        if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            if (!engineRun())
            {
                DestroyWindow(g_hWnd);
            }
        }
    }

    return (int)msg.wParam;
}

ATOM myRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = wndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = 0;
    wcex.lpszClassName = g_szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

    return RegisterClassEx(&wcex);
}

BOOL initInstance(HINSTANCE hInstance, int nCmdShow)
{
    g_hInst = hInstance; // 将实例句柄存储在全局变量中

    g_hWnd = CreateWindow(g_szWindowClass, g_szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if (!g_hWnd)
    {
        return FALSE;
    }

    ShowWindow(g_hWnd, nCmdShow);
    UpdateWindow(g_hWnd);

    return TRUE;
}

LRESULT CALLBACK wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc;
            hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
        }
        return 0;

    case WM_SIZE:
        onWindowSizeChanged();
        return 0;

    case WM_DESTROY:
        engineShutdown();
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
}

inline int getRectWidth(const RECT& rc)
{
    return rc.right - rc.left;
}

inline int getRectHeight(const RECT& rc)
{
    return rc.bottom - rc.top;
}

void centerWindow()
{
    // 最大可用的工作区
    RECT rectWork = { 0 };
    SystemParametersInfo(SPI_GETWORKAREA, 0, &rectWork, 0);

    // 先将窗口最大化充满
    int rectWork_Width = getRectWidth(rectWork);
    int rectWork_Height = getRectHeight(rectWork);
    MoveWindow(g_hWnd, rectWork.left, rectWork.top, rectWork_Width, rectWork_Height, FALSE);

    // 求得当前最大客户区
    RECT rectClient;
    GetClientRect(g_hWnd, &rectClient);

    // 调整到16：9
    int rectClient_Width = getRectWidth(rectClient);
    int rectClient_Height = getRectHeight(rectClient);

    const int desiredWidth = 1600; // 1280
    const int desiredHeight = 900;  // 720

#if 1 // 分辨率限制
    if (rectClient_Width > desiredWidth)
        rectClient_Width = desiredWidth;

    if (rectClient_Height > desiredHeight)
        rectClient_Height = desiredHeight;
#endif

    float currAsp = rectClient_Width / (float)rectClient_Height;
    const float needAsp = 16.0f / 9.0f;

    if (currAsp > needAsp) // 宽多了
        rectClient_Width = int(needAsp * rectClient_Height);
    else // 高多了
        rectClient_Height = int(rectClient_Width / needAsp);

    // 调整窗口大小
    RECT rectWindowAdj = { 0, 0, rectClient_Width, rectClient_Height };
    AdjustWindowRect(&rectWindowAdj, GetWindowLong(g_hWnd, GWL_STYLE), FALSE);

    // 工作区中央对齐
    int rectWindowAdj_Width = getRectWidth(rectWindowAdj);
    int rectWindowAdj_Height = getRectHeight(rectWindowAdj);

    int window_Left = rectWork.left + (rectWork_Width - rectWindowAdj_Width) / 2;
    int window_Top = rectWork.top + (rectWork_Height - rectWindowAdj_Height) / 2;

    MoveWindow(g_hWnd, window_Left, window_Top, rectWindowAdj_Width, rectWindowAdj_Height, FALSE);
}

